#include "Model.h"
#include "NeroEngine\NPlatform.h"
#include "NeroEngine\NDevice.h"
#include "NeroCore\NMemoryManager.h"
#include "TextureResourceManager.h"

using namespace Graphics;

RTTI_DEFINITIONS(Model)

#ifdef DX_11
#include "Buffer.h"
#include "Color.h"
#include <fstream>

Model::Model(const std::string& instanceName, const std::string& filePath)
	:Engine::NResource(instanceName,filePath),
	 mVertexBuffer(NULL),
	 mIndexBuffer(NULL),
	 mVertexCount(0),
	 mIndexCount(0),
	 mTextures(NULL),
	 mModel(NULL),
	 mInstanceCount(0)
{
}

Model::~Model(void)
{
	 Dinititalize();
}

bool Model::Initialize()
{
	if(!mIsInitialized)
	{
		D3DDevice* device = GetPlatform()->GetDevice()->As<D3DDevice>();

		bool result = LoadModel(mFilePath.c_str());
		if(!result)
		{
			//throw Win32Exception("Failed To Load Model");
			mVertexCount = 3;
			mIndexCount = mVertexCount;
		}

		mTextures = MY_NEW(Memory::HID_Rendering,"Texture") TextureArray();

		// Initialize the vertex and index buffer that hold the geometry for the triangle.
		result = InitializeBuffers();

		mIsInitialized = true;
		return result;
	}
	return false;
}

void Model::Dinititalize()
{
	ReleaseTexture();	// Release the model texture.

	ReleaseModel();

	DeinitializeBuffers(); // Release the vertex and index buffers.
}

void Model::Render()
{
	// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
	RenderBuffers();
}

void Model::Update(float delatTime)
{
}

bool Model::InitializeBuffers()
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;

	// Create the vertex array.
	vertices = new VertexType[mVertexCount];
	if(!vertices)
	{
		return false;
	}

	// Create the index array.
	indices = new unsigned long[mIndexCount];
	if(!indices)
	{
		return false;
	}

	if(mModel)
	{
		// Load the vertex array with data.
		for(int i = 0; i  < mVertexCount; i++)
		{
			vertices[i].Position = D3DXVECTOR3(mModel[i].X, mModel[i].Y, mModel[i].Z);
			vertices[i].Color = Colors::Blue;
			vertices[i].Texture = D3DXVECTOR2(mModel[i].Tu, mModel[i].Tv);
			vertices[i].Normal = D3DXVECTOR3(mModel[i].Nx, mModel[i].Ny, mModel[i].Nz);

			indices[i] = i;
		}
	}
	else
	{
		vertices[0].Position = D3DXVECTOR3(0.0f, 0.5f, 0.5f);  // Bottom left.
		vertices[0].Color = Graphics::Colors::Green;

		vertices[1].Position = D3DXVECTOR3(0.5f, -0.5f, 0.5f);  // Top middle.
		vertices[1].Color = Graphics::Colors::Green;	

		vertices[2].Position = D3DXVECTOR3(-0.5f, -0.5f, 0.5f);  // Bottom right.
		vertices[2].Color = Graphics::Colors::Green;
	}

	// Load the index array with data.
	indices[0] = 0;  // Bottom left.
	indices[1] = 1;  // Top middle.
	indices[2] = 2;  // Bottom right.

	// Set up the description of the static vertex buffer.
	vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * mVertexCount;
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = 0;
	vertexBufferDesc.MiscFlags = 0;
	vertexBufferDesc.StructureByteStride = 0;

	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;

	// Now create the vertex buffer.
	mVertexBuffer = MY_NEW(Memory::HID_Rendering,"Model Vertex Buffer") Buffer();
	mVertexBuffer->Initialize(sizeof(VertexType), vertices, mVertexCount, BufferType::VertexBuffer);

	// Set up the description of the static index buffer.
	indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * mIndexCount;
	indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	indexBufferDesc.CPUAccessFlags = 0;
	indexBufferDesc.MiscFlags = 0;
	indexBufferDesc.StructureByteStride = 0;

	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;
	indexData.SysMemPitch = 0;
	indexData.SysMemSlicePitch = 0;

	// Create the index buffer.
	mIndexBuffer = MY_NEW(Memory::HID_Rendering,"Model Index Buffer") Buffer();
	mIndexBuffer->Initialize(sizeof(unsigned long), indices, mIndexCount, BufferType::IndexBuffer);

	// Release the arrays now that the vertex and index buffers have been created and loaded.
	delete [] vertices;
	vertices = 0;

	delete [] indices;
	indices = 0;

	mInstanceCount = 4;

	InstanceType* instances = MY_NEW(Memory::HID_Rendering, "Instances") InstanceType[mInstanceCount];
	instances[0].position = D3DXVECTOR3(-1.5f, -1.5f, 5.0f);
	instances[1].position = D3DXVECTOR3(-1.5f,  1.5f, 5.0f);
	instances[2].position = D3DXVECTOR3( 1.5f, -1.5f, 5.0f);
	instances[3].position = D3DXVECTOR3( 1.5f,  1.5f, 5.0f);

	mInstanceBuffer = MY_NEW(Memory::HID_Rendering, "Instance Buffer") Buffer();

	// Set up the description of the instance buffer.
	D3D11_BUFFER_DESC  instanceBufferDesc;
	instanceBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	instanceBufferDesc.ByteWidth = sizeof(InstanceType) * mInstanceCount;
	instanceBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	instanceBufferDesc.CPUAccessFlags = 0;
	instanceBufferDesc.MiscFlags = 0;
	instanceBufferDesc.StructureByteStride = 0;
	
	D3D11_SUBRESOURCE_DATA instanceData;
	instanceData.pSysMem = instances;
	instanceData.SysMemPitch = 0;
	instanceData.SysMemSlicePitch = 0;

	mInstanceBuffer->Initialize(instanceBufferDesc, instanceData);

	MY_DELETE(instances);
	instances = NULL;

	return true;
}

void Model::DeinitializeBuffers()
{
	// Release the index buffer.
	if(mIndexBuffer)
	{
		mIndexBuffer->Release();
		mIndexBuffer = NULL;
	}

	// Release the vertex buffer.
	if(mVertexBuffer)
	{
		mVertexBuffer->Release();
		mVertexBuffer = NULL;
	}

	// Release the instance buffer.
	if(mInstanceBuffer)
	{
		mInstanceBuffer->Release();
		mInstanceBuffer = NULL;
	}
}

void Model::RenderBuffers()
{
	unsigned int stride[2];
	unsigned int offset[2];

	ID3D11Buffer* bufferPointers[2];

	D3DDevice* device = GetPlatform()->GetDevice()->As<D3DDevice>();

	// Set vertex buffer stride and offset.
	stride[0] = sizeof(VertexType); 
	stride[1] = sizeof(InstanceType);

	offset[0] = 0;
	offset[1] = 0;

	bufferPointers[0] = mVertexBuffer->GetResource();
	bufferPointers[1] = mInstanceBuffer->GetResource();
    
	// Set the vertex buffer to active in the input assembler so it can be rendered.
	device->GetDeviceContext()->IASetVertexBuffers(0, 1/*2*/, bufferPointers, stride, offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	ID3D11Buffer* ibuffer = mIndexBuffer->GetResource();
	device->GetDeviceContext()->IASetIndexBuffer(ibuffer, DXGI_FORMAT_R32_UINT, 0);

	// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	device->GetDeviceContext()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}

TextureArray* Model::GetTextures()
{ 
	return mTextures;
}

void Model::SetTexture(Texture* texture)
{
	mTextures->AddTexture(texture);
}

Texture* Model::GetTexture(int index)
{
	if(!mTextures->Size())
	{
		return NULL;
	}

	return mTextures->operator[](index);
}


int Model::GetIndexCount() const
{
	return mIndexCount;
}

int Model::GetVertexCount() const
{
	return mVertexCount;
}

int Model::GetInstanceCount() const
{
	return mInstanceCount;
}

bool Model::LoadTexture(const char* textureName)
{
	D3DDevice* device = GetPlatform()->GetDevice()->As<D3DDevice>();
	if(device == NULL)
	{
		return false;
	}
	if(textureName == "")
	{
		return false;
	}
	bool result = mTextures->AddTexture(textureName);
	if(!result)
	{
		return false;
	}
	return true;
}

bool Model::LoadModel(const char* modelName)
{
	ReleaseModel();

	std::ifstream fin;
	char input;
	int i;

	// Open the model file.
	fin.open(modelName);
	
	// If it could not open the file then exit.
	if(fin.fail())
	{
		return false;
	}

	// Read up to the value of vertex count.
	fin.get(input);
	while(input != ':')
	{
		fin.get(input);
	}

	// Read in the vertex count.
	fin >> mVertexCount;

	// Set the number of indices to be the same as the vertex count.
	mIndexCount = mVertexCount;

	// Create the model using the vertex count that was read in.
	mModel = new ModelType[mVertexCount];
	
	if(!mModel)
	{
		return false;
	}

	// Read up to the beginning of the data.
	fin.get(input);
	while(input != ':')
	{
		fin.get(input);
	}
	fin.get(input);
	fin.get(input);

	// Read in the vertex data.
	for(i=0; i<mVertexCount; i++)
	{
		fin >> mModel[i].X >> mModel[i].Y >> mModel[i].Z;
		fin >> mModel[i].Tu >> mModel[i].Tv;
		fin >> mModel[i].Nx >> mModel[i].Ny >> mModel[i].Nz;
	}

	// Close the model file.
	fin.close();

	return true;
}

void Model::ReleaseTexture()
{
	if(mTextures->Size() > 0)
	{
		mTextures->Deinitialize();
		MY_DELETE(mTextures);
	}
}

void Model::ReleaseModel()
{
	if(mModel != NULL)
	{
		delete [] mModel;
		mModel = NULL;
	}
}

#elif defined(DX_9)

Model::Model()
	:Engine::NResource("",""),
	 mMaterialCount(0),
	 mVertexCount(0),
	 mIndexCount(0)
{

}

Model::Model(const std::string& instanceName, const std::string& filePath)
	:Engine::NResource(instanceName,filePath),
	  mMaterialCount(0)
{
}

Model::~Model(void)
{
}

void Model::Initialize()
{
	Load(mFilePath);
}

void Model::Dinititalize()
{

}

void Model::Update(float gameTime)
{

}

void Model::Render()
{
	D3DDevice* nDevice = GetPlatform()->GetDevice()->As<D3DDevice>();
	for(UINT i = 0; i < mMaterialCount; i++)
	{	
		if(nDevice)
		{
			LPDIRECT3DDEVICE9 device = nDevice->GetDevice();
			nDevice->GetDevice()->SetMaterial( &mMaterials[i]);
			if(mMesh)
			{
				if(mTextures.size())
				{
					nDevice->GetDevice()->SetTexture(0, mTextures[i]->GetResource());
				}

				mMesh->DrawSubset(i);
			}
		}
	}
}

Texture* Model::GetTexture()
{
	if(mTextures.size() == 0)
	{
		return NULL;
	}
	return mTextures[0];
}

void Model::SetTexture(Texture* texture)
{
	mTextures.push_back(texture);
}

LPD3DXMESH Model::GetMesh()
{
	return mMesh;
}

DWORD Model::GetMaterialCount()
{
	return mMaterialCount;
}

int Model::GetIndexCount() const
{
	return mIndexCount;
}

int Model::GetVertexCount() const
{
	return mVertexCount;
}

void Model::Load(const std::string& filePath)
{
	HRESULT result;
	ID3DXBuffer* pMaterialBuffer = NULL;
	D3DDevice* device = GetPlatform()->GetDevice()->As<D3DDevice>();
	
	result = D3DXLoadMeshFromX(filePath.c_str(), D3DXMESH_SYSTEMMEM, device->GetDevice(), NULL, &pMaterialBuffer, NULL, &mMaterialCount, &mMesh);
	CHECK(result);
	if(!pMaterialBuffer)
	{
		return;
	}

	D3DXMATERIAL* pMaterials = (D3DXMATERIAL*)pMaterialBuffer->GetBufferPointer();
	mMaterials = new D3DMATERIAL9[mMaterialCount]; 
	Texture* texture = NULL;

	// Copy the materials and textures from the buffer to the member arrays 
	for ( DWORD i = 0; i < mMaterialCount; i++ ) 
	{
		mMaterials[i] = pMaterials[i].MatD3D; 
		mMaterials[i].Ambient = mMaterials[i].Diffuse; 
		// Create the texture if it exists 
		if( pMaterials[i].pTextureFilename ) 
		{
			std::string textureFileName = pMaterials[i].pTextureFilename;

			texture = TextureResourceManager::GetInstance()->GetResource(textureFileName);
			if(texture)
			{
				mTextures.push_back(texture);
			}
			else
			{
				std::string pathToTexture = "..\\..\\assets\\textures\\";
				pathToTexture += textureFileName;

				TextureResourceManager::GetInstance()->Add(textureFileName, pathToTexture);

				texture = TextureResourceManager::GetInstance()->GetResource(textureFileName);
				if(texture)
				{
					texture->Initialize();
					mTextures.push_back(texture);
				}
			}
		}
	}
	pMaterialBuffer->Release();
}
#endif